#include "ship_template.h"
#include "slot_template.h"
#include "io.h"
#include "json/value.h"
#include <stdexcept>
#include <algorithm>

using namespace std;

ShipTemplate::ShipTemplate( const Json::Value & _shipTemplate, string _name ) :
	name(_shipTemplate["Name"].asString()),
	shipClass(_shipTemplate["Class"].asInt()),
	maxHp(_shipTemplate["MaxHp"].asInt()),
	sides(computeSides(_shipTemplate["Sides"])),
	controlTowers(_shipTemplate["ControlTowers"].asInt()),
	cost(computeCost(this))
{
	if ( _name != "" ) name = _name;

	// Value checks
	if ( shipClass < MIN_SHIP_CLASS || shipClass > MAX_SHIP_CLASS ||
		 cost < 1 || maxHp < 1 || controlTowers < 0 )
		throw runtime_error("ShipTemplate(const Json::Value &,string)");
	// FIXME: better checks on max maxhp ( 1:100 ? )
}

ShipTemplate::ShipTemplate( int _shipClass, int _maxHp, int _controlTowers, 
							const array<vector<SlotTemplate>,ALL_SIDES> & _sides, string _name ) :
	name(_name),
	shipClass(_shipClass),
	maxHp(_maxHp),
	sides(_sides),
	controlTowers(_controlTowers),
	cost(computeCost(this))
{
	// Value checks
	if ( shipClass < MIN_SHIP_CLASS || shipClass > MAX_SHIP_CLASS ||
		 cost < 1 || maxHp < 1 || controlTowers < 0 )
		throw runtime_error("ShipTemplate(const Json::Value &,string)");
}

ShipTemplate::ShipTemplate( const ShipTemplate & _ship ) :
	name(_ship.name),
	shipClass(_ship.shipClass),
	maxHp(_ship.maxHp),
	sides(_ship.sides),
	controlTowers(_ship.controlTowers),
	cost(_ship.cost) {}

bool ShipTemplate::operator== (const ShipTemplate & other) const {
	if ( ! (
		name			== other.name			&&
		shipClass		== other.shipClass		&&	
		maxHp			== other.maxHp			&&
		controlTowers	== other.controlTowers	&&
		cost			== other.cost ) ) return false;

	for ( int i = 0; i < ALL_SIDES; i++ ) {
		if ( sides[i].size() != sides[i].size() ) return false;

		vector<SlotTemplate> own = sides[i];
		vector<SlotTemplate> oth = other.sides[i];

		std::sort( own.begin(), own.end(), SlotTemplate::compare );
		std::sort( oth.begin(), oth.end(), SlotTemplate::compare );

		if ( own != oth ) return false;
	}
	return true;
}

Json::Value ShipTemplate::convertToJson() const {
	Json::Value ship;

	ship["Name"] = name;
	ship["Type"] = "ShipTemplate";
	// This is saved only for human inspection, but it is not loaded
	ship["Cost"] = cost;
	ship["Class"] = shipClass;
	ship["MaxHp"] = maxHp;
	ship["ControlTowers"] = controlTowers;

	for ( unsigned int i = MIN_SIDE; i < (unsigned int)ALL_SIDES; i++ ) {
		unsigned int j = 0;
		for ( auto &it : sides.at(i) ) {
			ship["Sides"][i][j] = it.convertToJson();
			j++;
		}
	}

	return ship;
}

ShipTemplate ShipTemplate::addSlotTemplate( int _side, const SlotTemplate & _slot ) const {
	array<vector<SlotTemplate>,ALL_SIDES> temp = sides;

	temp.at(_side).push_back(_slot);

	return ShipTemplate( shipClass, maxHp, controlTowers, temp, name );  
}

ShipTemplate ShipTemplate::rmSlotTemplate( int _side, int _slot ) const {
	array<vector<SlotTemplate>,ALL_SIDES> temp = sides;

	temp.at(_side).erase(temp.at(_side).begin() + _slot);

	return ShipTemplate( shipClass, maxHp, controlTowers, temp, name );  
}

ShipTemplate ShipTemplate::addSymmetricSlotTemplate( int _side, SlotTemplate _slot ) const {
	array<vector<SlotTemplate>,ALL_SIDES> temp = sides;
	// This function is SYMMETRIC
	// _side 0 is FRONT
	// _side 1 is FRONT LEFT + FRONT RIGHT
	// _side 2 is BACK LEFT + BACK RIGHT
	// _side 3 is BACK
	// _side 4 is BACK LEFT + BACK RIGHT
	// _side 5 is FRONT LEFT + FRONT RIGHT

	int otherSide = ( 6 - _side )%6;

	temp.at(_side).push_back(_slot);

	if ( otherSide != _side ) {
		_slot.invertDirections();
		temp.at(_side).push_back(_slot);
	}

	return ShipTemplate( shipClass, maxHp, controlTowers, temp, name );  
}

ShipTemplate ShipTemplate::rmSymmetricSlotTemplate( int _side, int _slot ) const {
	array<vector<SlotTemplate>,ALL_SIDES> temp = sides;
	// This function is SYMMETRIC
	// _side 0 is FRONT
	// _side 1 is FRONT LEFT + FRONT RIGHT
	// _side 2 is BACK LEFT + BACK RIGHT
	// _side 3 is BACK
	// _side 4 is BACK LEFT + BACK RIGHT
	// _side 5 is FRONT LEFT + FRONT RIGHT

	int otherSide = ( 6 - _side )%6;

	temp.at(_side).erase(temp.at(_side).begin() + _slot);

	if ( otherSide != _side ) 
		temp.at(otherSide).erase(temp.at(otherSide).begin() + _slot);

	return ShipTemplate( shipClass, maxHp, controlTowers, temp, name );  
}

ShipTemplate ShipTemplate::toggleSlotTemplate( int _side, int _slot ) const {
	array<vector<SlotTemplate>,ALL_SIDES> temp = sides;

	temp.at(_side).at(_slot).toggleAllowedCannons();

	return ShipTemplate( shipClass, maxHp, controlTowers, temp, name );  
}

ShipTemplate ShipTemplate::toggleSymmetricSlotTemplate( int _side, int _slot ) const {
	array<vector<SlotTemplate>,ALL_SIDES> temp = sides;
	// This function is SYMMETRIC
	// _side 0 is FRONT
	// _side 1 is FRONT LEFT + FRONT RIGHT
	// _side 2 is BACK LEFT + BACK RIGHT
	// _side 3 is BACK
	// _side 4 is BACK LEFT + BACK RIGHT
	// _side 5 is FRONT LEFT + FRONT RIGHT

	int otherSide = ( 6 - _side )%6;

	temp.at(_side).at(_slot).toggleAllowedCannons();

	if ( otherSide != _side ) 
		temp.at(otherSide).at(_slot).toggleAllowedCannons();

	return ShipTemplate( shipClass, maxHp, controlTowers, temp, name );  
}

ShipTemplate ShipTemplate::setMaxHp( int _hp ) const {
	if ( _hp < 1 ) throw invalid_argument("ShipTemplate::setMaxHp(int)");

	return ShipTemplate( shipClass, _hp, controlTowers, sides, name );  
}

ShipTemplate ShipTemplate::setTowers( int _towers ) const {
	if ( _towers < 0 ) throw invalid_argument("ShipTemplate::setTowers(int)");

	return ShipTemplate( shipClass, maxHp, _towers, sides, name );  
}

vector<array<string,4>> ShipTemplate::getSlotTemplateSummaries() const {
	vector<array<string,4>> summaries;
	
	array<string,4> temp;
	for ( int i = MIN_SIDE; i < ALL_SIDES; i++ )
		for ( auto &it : sides.at(i) ) {
			temp[0] = STRING_SIDE(i);
			swap_ranges(temp.begin()+1, temp.end(), it.getSummary().begin());
			summaries.push_back(temp);
		}

	return summaries;
}

array<string,3> ShipTemplate::getSummary() const {
	array<string,3> summary;
	
	summary[0] = name;

	summary[1] = STRING_CLASS(shipClass);

	summary[2] = std::to_string(cost);

	return summary;
}

int ShipTemplate::getSlotTemplatesNumber() const {
	int k = 0;

	for ( int i = MIN_SIDE; i < ALL_SIDES; i++ )
		k += sides.at(i).size();

	return k;
}

int ShipTemplate::getSlotTemplatesNumber(int _side) const {
	return sides.at(_side).size();
}

int ShipTemplate::getCost() const 						{return cost;}
int ShipTemplate::getShipTemplateClass() const 			{return shipClass;}
int ShipTemplate::getMaxHp() const 						{return maxHp;}
vector<SlotTemplate> ShipTemplate::getSide(int i) const {return sides.at(i);}
int ShipTemplate::getTowers() const			 			{return controlTowers;}
const SlotTemplate & ShipTemplate::getSlotTemplate(int i, int j) const {return sides.at(i).at(j);}

int ShipTemplate::computeCost( const ShipTemplate * _shipTemplate ) {
	int cost = 0;

	for ( int i = MIN_SIDE; i < ALL_SIDES; i++ )
		for ( auto &it : _shipTemplate->sides[i] )
			cost += it.getCost();

	cost += TOWER_SLOT_COST(_shipTemplate->controlTowers);

	cost += HP_SHIP_COST(_shipTemplate->maxHp);

	// MAY NEED CLASS BASED COST

	return cost;
}

array<vector<SlotTemplate>,ALL_SIDES> ShipTemplate::computeSides( const Json::Value & _sides ) {
	array<vector<SlotTemplate>,ALL_SIDES> temp;

	for ( unsigned int i = MIN_SIDE; i <(unsigned int)ALL_SIDES; i++ ) {
		for ( unsigned int j = 0; j < _sides[i].size(); j++ ) {
			temp.at(i).push_back( SlotTemplate(_sides[i][j]) );
		}
	}

	return temp;
}
